iT邦幫忙

2022 iThome 鐵人賽

DAY 20
3
Modern Web

真的好想離開 Vue 3 新手村 feat. CompositionAPI系列 第 20

Day 20: 在發 API 之前 - 先學 axios 基礎與封裝管理 API

  • 分享至 

  • xImage
  •  

前言

接下來幾篇文章以「在 Vue 3 Composition API 處理非同步( 發 API )」為主軸,會從新手的角度出發,告訴大家可以在哪些地方、時機發 API,會提到可能遇到的坑、該如何處理。

預計會整理 Vue 3 的 Lifecycle Hooks 和 Vue Router 的 Navigation Guard。
最後才會講到狀態管理器(Pinia),相信大家也會在過程中發現他的好用之處!

今天算是過渡篇章,會介紹 axios 基礎語法與設定,以及自己如何封裝管理 API,之後的文章都會直接用封裝好的寫法來處理!


行前須知 - API

  1. 第二屆 F2E 提供的旅館預約 API
    這幾篇主要會使用第二屆 F2E 提供的旅館預約 API 來示範,大家可以到這裡看 API 文件,雖然第二屆 F2E 已經結束了,但 API 還是可以使用,也依然可以註冊取得 token,真的很貼心~
  2. JSONPlaceholder
    講解簡單的語法時,會使用 JSONPlaceholder 的 API,主要會用 GET 來取得他提供的資料,但有假的POST、PUT、PATCH、DELETE 可以練習。

在正式發 API 之前,我們要先來封裝 axios!

axios

Promise based HTTP client for the browser and node.js

axios 是一個以 promise 為基礎的 HTTP 請求工具,可以用來傳送 HTTP 要求和接收 HTTP 回應,可以用在瀏覽器和 node.js 環境中,在瀏覽器環境中使用 XMLHttpRequest。

安裝

  1. 透過套件管理器安裝:
    使用 vite 或 Vue Cli 建構工具也可以直接安裝,不用找特殊的 plugin。
    npm install axios
    yarn add axios
    
  2. 使用 CDN
    <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
    

這樣就可以在專案中直接引用使用了!

import axios from "axios";

Syntax - Axios API

axios(config) //url 會寫在 config 裡面
axios(url[, config])

//也可以直接從 axios.method()
axios.get(url[, config])
axios.post(url[, data[, config]])

範例(使用 JSONPlaceholder):

//axios(config)
axios({
  method: "get",
  url: "https://jsonplaceholder.typicode.com/todos",
})
  .then((response) => console.log(response.data))
  .catch((error) => console.log(error));
  
//axios.get(url[, config])
axios
  .get("https://jsonplaceholder.typicode.com/todos")
  .then((response) => console.log(response.data))
  .catch((error) => console.log(error));
  • 常用 Request Config

    • url:唯一必填 config,request 的網址
    • method:預設為 get
    • data:Request 時作為 request body 送出,只在使用 PUTPOSTDELETEPATCH 這幾個 HTTP 方法時可以用
    • baseURL:
      這段 baseURL 會被放到 url 的最前面,除非提供的 url 是絕對路徑。
      舉例來說,如果 baseURL 為 "https://challenge.thef2e.com/api/thef2e2019/stage6/",當 url 帶入 "/rooms",這次 request 的完整 url 就會是 "https://challenge.thef2e.com/api/thef2e2019/stage6/rooms"
    • headers:HTTP Header,可以用來紀錄資料和設定,特別講解等等會使用到的:
      • Accept: 客戶端可以接受的 response type (Client Request-header)
      • Content-type: 當下 request 或 response 的 payload 內容格式
    • timeout:
      • 預設為 0,單位 milliseconds
      • 如果 request 會超過這個時間,就停止 request
    • 更多 config
  • 回傳值
    axios 會回傳 Promise

    1. .then.catch 去處理成功和失敗的結果
    2. async+await 搭配 try-catch 處理
try {
  const response = await axios({
    method: "get",
    url: "https://jsonplaceholder.typicode.com/todos",
  });
  console.log(response.data);
} catch (error) {
  console.log(error);
}

如果對 Promise 和 async function 不熟悉,推薦下列文章:


Syntax - The Axios Instance

在開發專案時,我們在處理 API 時,部份設定通常是相同的(例如:baseURL、header 等等),為了避免一直寫重複的程式碼,我們可以創建一個 axios 實例。

axios.create([config])

範例(使用六角學院旅館預約 API):

//創建實例
const hotelAPI = axios.create({
  baseURL: "https://challenge.thef2e.com/api/thef2e2019/stage6",
  headers: {
    "Content-Type": "application/json; charset=utf-8",
    Accept: "application/json",
  },
});

//使用方法
const res = await hotelAPI.get("/rooms")
const res = await hotelAPI.delete("/rooms")

config 的部份可以參考上一段的常用 Request Config,這裡只重提上面範例用到的部份:

  • baseURL:
    這段 baseURL 會被放到 url 的最前面,除非 url 是絕對路徑;舉例來說,如果我使用 hotelAPI.get("/rooms"),這次 request 的 url 就會是 "https://challenge.thef2e.com/api/thef2e2019/stage6/rooms"
  • headers:HTTP Header,可以用來紀錄資料和設定,特別講解以上使用到的:
    • Accept: 客戶端可以接受的 response type (Client Request-header)
    • Content-type: 當下 request 或 response 的 payload 內容格式

如果專案內用到不同 API,也可以創建多個 axios 實例來管理。

封裝起來,發 API 更簡潔

我們已經免去重複寫 config 的部份,還可以封裝不同的 request 方法,讓 API 在使用上更簡潔。

預期使用方式:

const data = await hotelAPI.GET("/rooms");

這裡主要示範封裝旅館預約 API 會用到的 GET、POST 跟 DELETE(使用全大寫是為了可以跟 axios 原本的 method 做區分),大家可以自己封裝其他會使用到的方法,或是需要特殊設定的 request,例如:登入、FormData 多媒體上傳。

export default {
  async GET(url, params) {
    try {
      const res = await API.get(url, {
        params,
      });
      return res.data;
    } catch (res) {
      return Promise.reject(res.message);
    }
  },
  async DELETE(url, params) {
    try {
      const res = await API.delete(url, {
        params,
      });
      return res.data;
    } catch (res) {
      return Promise.reject(res.message);
    }
  },
  async POST(...arg) {
    try {
      const res = await API.post(...arg);
      return res.data;
    } catch (res) {
      return Promise.reject(res.message);
    }
  },
};

攔截器 interceptors

透過 axios 攔截器,可以在送出 request 之前、收到 response 後,還沒進入 then()catch() 處理之前,將一切動作攔截下,處理一些事情。

  • request 攔截器:

如果網站有會員登錄功能,在會員使用網站時,會觸發不同事件,過程中送出不同 API(如:查看購買清單、取消購買),而後端需要透過 token 驗證會員的身份,才會回傳相應的資料。
透過攔截器,我們可以在每次送出 request 之前,將請求攔截下來,將 token 加入 header,就能順利通過驗證,取得 API 資料。

  • response 攔截器:

可以在這裡攔截後端回傳的 response,通常會針對常見的 HTTP 狀態碼做處理。

//request 攔截器
axios.interceptors.request.use(function (config) {
    // 在送出 request 之前可以在這裡攔截處理
    return config;
  }, function (error) {
    // 如果 request 出現 error
    // 可以在這裡攔截處理
    return Promise.reject(error);
  });

//response 攔截器
axios.interceptors.response.use(function (response) {
    // 回傳的 status code 在 2xx 區間會觸發這個函式
    // 可以在這裡拿到 response 做處理 
    return response;
  }, function (error) {
    // 回傳的 status code 不在 2xx 區間會觸發這個函式
    // 可以在這裡拿到 response error 做處理
    return Promise.reject(error);
  });

*補充 - status code:

範例 - F2E 旅館預約

這一段以六角 F2E 旅館預約 API 為例。
我們可以對剛剛建立的 axios 實例設定攔截器:

  • request 攔截
    request 時需要帶有 token 才可以順利拿到資料,所以我們可以在每次 request 之前,對 config 新增 Authorization 屬性,值為有效的 token。
API.interceptors.request.use(
  (config) => {
    let access_token = 這裡放跟六角申請的 token
    if (access_token) {
      config.headers.Authorization = `Bearer ${access_token}`;
    }
    return config;
  },
  function (error) {
    return Promise.reject(error);
  }
);
  • response 攔截
    可以在 reponse 攔截器的 error 攔截函式中,針對收到的 status code 做不同情況的處理。
API.interceptors.response.use(
  function (response) {
    return response;
  },
  function (error) {
    if (error.response) {
      switch (error.response.status) {
        //可以在這裡針對不同 status code 做處理
        case 401:
          alert("token 無效");
          console.log(error.message);
          break;
        case 404:
          alert("頁面不存在");
          console.log(error.message);
          break;
        case 500:
          alert("程式發生問題");
          console.log(error.message);
          break;
        default:
          alert("程式發生問題");
          console.log(error.message);
      }
    }
    if (!window.navigator.onLine) {
      alert("請重新連線後重整網頁");
      return;
    }
    return Promise.reject(error);
  },
);

結尾

今天就到這裡告一段落,之後幾篇會直接用封裝好的 axios 實例跟 method 來發 API,我們明天見拉~

封裝方式是參考饅頭大大的文章:

參考資料


上一篇
Day 19: 你可能不知道的 v-model - 為何多選綁定陣列不能用 reactive()?
下一篇
Day 21: 來發 API 吧!Async Composition API setup() feat. <Suspense>
系列文
真的好想離開 Vue 3 新手村 feat. CompositionAPI31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

我要留言

立即登入留言